package com.pehulja.mmdo.coursework.math;

import com.pehulja.mmdo.coursework.gui.graph.Line;
import com.pehulja.mmdo.coursework.math.checkingResults.MusculoskeletalChecking;
import com.pehulja.mmdo.coursework.math.exceptions.AlgoritmExceptions.InitialPlanCannotbeFinded;
import com.pehulja.mmdo.coursework.math.exceptions.AlgoritmExceptions.LimitlessnessOfLinearForm;
import com.pehulja.mmdo.coursework.math.exceptions.AlgoritmExceptions.UnSupportedCalculationExceptions;
import com.pehulja.mmdo.coursework.math.exceptions.InvalidNumberArgmentsException;
import com.pehulja.mmdo.coursework.math.exceptions.TaskHasNotSpecialForm;
import com.pehulja.mmdo.coursework.math.matrix.Matrix;
import com.pehulja.mmdo.coursework.math.matrix.MatrixMathematics;
import com.pehulja.mmdo.coursework.math.matrix.NoSquareException;
import com.pehulja.mmdo.coursework.math.matrix.Operations;
import org.apache.commons.math.linear.*;

import java.util.*;

public class Solver {

    private LinearProgramTask task, dualTask;
    private List<SimplexTable> simplexTables;
    private double[] solutionEquationsDualTask;
    private SimplexTable table;
    public static final double Error = 0.01;
    private LinearProgramTask canonicalLinearProgramTask;
    /**
     * Checking for stability
     */
    private List<Line> graphs;
    private List<ArrayRealVector> crossingOfLinePoints;

    
    public static void main(String[] args) throws InvalidNumberArgmentsException, CloneNotSupportedException, LimitlessnessOfLinearForm, InitialPlanCannotbeFinded, NoSquareException {
    final int size = 10;
        double [][]A= new double[size][size];
    Random random = new Random();
    for(int i = 0; i < size; i++)
        for(int j = 0; j < size; j++)
            A[i][j] = random.nextInt()%100;
    for(double[] i:A){
        System.out.println(Arrays.toString(i));
    }
        System.out.println("OK!");
    RealMatrix matrixAfs = new Array2DRowRealMatrix(MatrixMathematics.inverse(new Matrix(A)).getValues());
        System.out.println(matrixAfs.toString());
    
    
    }

    public LinearProgramTask getTask() {
        return task;
    }

    public void setTask(LinearProgramTask task) {
        this.task = task;
    }

    public List<SimplexTable> getSimplexTables() {
        return simplexTables;
    }

    public SimplexTable getTable() {
        return table;
    }

    public void solve() throws InvalidNumberArgmentsException, CloneNotSupportedException, LimitlessnessOfLinearForm, NoSquareException, InitialPlanCannotbeFinded, TaskHasNotSpecialForm {
        if(!checkIfTaskHasSpecialFormToCurrentMethod())
            throw new TaskHasNotSpecialForm();
        List <Integer> basisComponents = null;
        table = new SimplexTable();
        if(task.checkIfTaskHasSpecialForm()){
            int n = task.getGoalFunction().getVariablesCount();
            int m = task.getBusinessRestrictions().size();
            basisComponents = new ArrayList<Integer>();
            for(int i = n; i < n + m; i++)
                basisComponents.add(i);
            canonicalLinearProgramTask = task.convertToCanonicalForm();
            
            dualTask = canonicalLinearProgramTask.getDualTask();
        }else{
            // Mock now
            //table.setBasisComponents(Arrays.asList(0, 2));
            canonicalLinearProgramTask = task.convertToCanonicalForm();
            dualTask = canonicalLinearProgramTask.getDualTask();
            //basisComponents = Arrays.asList(0, 2);
            basisComponents = solveEquations(dualTask.getBusinessRestrictions(), dualTask.getGoalFunction().getListVarsId());
            if(basisComponents == null)
                throw new InitialPlanCannotbeFinded();
        }
        table.setBasisComponents(basisComponents);
        //table.setBasisComponents(Arrays.asList(0, 2));
        table.marshallTask(canonicalLinearProgramTask);
        simplexTables = new ArrayList<SimplexTable>();
        System.out.println(canonicalLinearProgramTask.toString());
        while (true) {
            table.count();
            simplexTables.add(table.copy());
            //table.countN();
            if (table.isOptimalPlanFinded()) {
                break;
            } else {
                table.recountMatrixA();
            }
        }
    }

    public Double getFunctionOptimalValue() {
        return Matrix.round(task.getGoalFunction().countValue(getOptimalPlan()));
    }

    public List<Integer> getBasisVectorsId() {
        return  getBasisVectorsIdExtended().subList(0, task.getBusinessRestrictions().size());
    }

    public double[] getValuesOptimalResultPlanBasicComponents() {
        return Arrays.copyOfRange(getValuesOptimalResultPlanBasicComponentsExtended(), 0, task.getBusinessRestrictions().size());
    }

    public double[] getOptimalPlan() {
        return Arrays.copyOfRange(getOptimalPlanExtended(), 0, task.getGoalFunction().getVariablesCount()); 
    }
    
    public double[] getValuesOptimalResultPlanBasicComponentsExtended() {
        return table.getValuesOptimalResultPlanBasicComponents();
    }
    public List<Integer> getBasisVectorsIdExtended() {
        return table.getBasisVectorsId();
    }
    public double[] getOptimalPlanExtended() {
        return table.getOptimalPlan();
    }
   

    /**
     * Creation of the initial basic plan of a direct task by a
     * solutionOfEquationsDualTask of system of inequalities of a dual task and
     * searching for suitable basic components This method implements searching
     *
     * @param equations Collection of equations of dual task business
     * requirements that which make a system of equetions
     * @param variables Collection that stores ids of variables that uses in
     * equations. The are used generally only to get number of variables as
     * numbers of variables while generating a number of combinations
     * @return TODO:// Now it returns only roots. It must returns indexes of
     * basic components else
     */
    private List<Integer> solveEquations(List<Equation> equations, List<Byte> variables) {
        RealMatrix coefficient = null;          // Store matrix of coefficients of basic requirements (only left part in equations)
        DecompositionSolver solver;             // Apache Math solver
        RealVector constants, solutionOfEquationsDualTask = null;  // Apache math vectors that stores coefficients of right part of equations and roots of system
        int equationsCount;                     //Counter that only stores number of equations in system
        List<Integer> container = null;         // Container that stores ids of the estimated indexes of basic components
        boolean solutionFounded = false;        // Flah that comes true when roots are founded
        Set<ContainerOfSet> equationsToSolveSets = new HashSet<ContainerOfSet>(); // Stores all combinations that were used while searching roots but were wrong
        int nCombinations;                      //Counter that stores maximum number of combinations with current number of equations and variables

        equationsCount = equations.size();
        nCombinations = Operations.factorial(equationsCount) / (Operations.factorial(variables.size()) * Operations.factorial(equationsCount - variables.size()));

        /*for (Equation equation : equations) // All equations have to be in standard form and with all variables even if it may be 0x(n)^0.0
        {
            equation.toStandardForm();
        }*/

        while (equationsToSolveSets.size() < nCombinations) {  //Working while it may be one more combination of
            solutionFounded = true;
            do {
                container = Arrays.asList(Operations.generateRandomUniqSequence(0, equationsCount-1, variables.size()));
            } while (equationsToSolveSets.contains(new ContainerOfSet(container)));   //Getting a new unique sequence of components that was not yet used

            coefficient = new Array2DRowRealMatrix(container.size(), equations.get(container.get(0)).getLeft().getVariablesCount());
            constants = new ArrayRealVector();

            for (int i = 0; i < container.size(); i++) {  //Filling matrix with coefficients according to chosen possible basic components
                coefficient.setRow(i, equations.get(container.get(i)).getLeft().getListFactorsArray());
                constants = constants.append(equations.get(container.get(i)).getRight().getListFactorsArray()); // appending right factor of every equation
            }
            if(coefficient.getDeterminant()!=0){
                solver = new LUDecompositionImpl(coefficient).getSolver(); // Trying to solve generated system
                solutionOfEquationsDualTask = solver.solve(constants);
                if (solutionOfEquationsDualTask == null) {
                    equationsToSolveSets.add(new ContainerOfSet(container));   // If it can`t be resolved add this combination of components to container
                    continue;
                }

                for (int i = 0; i < equationsCount; i++) { // Checking if roots suites to other non-basic group of inequalities
                    if (!container.contains(i)) {
                        solutionFounded = equations.get(i).checkIfItSuites(solutionOfEquationsDualTask.getData());
                        if(!solutionFounded)
                            break;
                    }
                }
            }else
                solutionFounded = false;
            if (solutionFounded) // If roots suits - exit the algorithm
            {
                break;
            } else {
                equationsToSolveSets.add(new ContainerOfSet(container)); // Otherwise add this combination of components to container
            }
        }
        if (solutionOfEquationsDualTask == null || solutionFounded == false) {
            return null;
        } else {
            this.solutionEquationsDualTask = solutionOfEquationsDualTask.getData();
            return container;
        }
    }

    public void stabilityResearch(Integer[] indexes) {
        int rowNumber = task.getBusinessRestrictions().size();
        int colNumber = indexes.length;

        RealMatrix matrixA = task.getBusinessRestrictionsMatrix(Arrays.asList(indexes), false);

        RealVector vectorX = new ArrayRealVector(getOptimalPlan());
        double[] allX = vectorX.getData();
        vectorX = new ArrayRealVector();
        for (int i : indexes) {
            vectorX = vectorX.append(allX[i]);
        }

        crossingOfLinePoints = new ArrayList<ArrayRealVector>();
        ArrayRealVector temp;
        for (int i = 0; i < rowNumber; i++) {
            for (int j = i + 1; j < rowNumber; j++) {
                temp = solveEquations(matrixA.getSubMatrix(i, j, 0, colNumber - 1), vectorX.getSubVector(i, j + 1));
                if (temp != null) {
                    crossingOfLinePoints.add(temp);
                }
            }
        }
        
        graphs = new ArrayList<Line>();

        try {
            List<Equation> equations = new ArrayList<Equation>();
            for (int i = 0; i < rowNumber; i++) {
                Equation equation = new Equation();
                double data[][] = new double[colNumber][3];
                double factors[] = matrixA.getRow(i);
                for (int j = 0; j < colNumber; j++) {
                    data[j] = new double[]{j, factors[j], 1};
                }
                equation.setLeft(new PolinomialFunc(data));
                equation.setRight(new PolinomialFunc(new double[][]{{0, vectorX.getData()[i], 0}}));
                equation.setInequalitySight(Equation.InequalitySight.BIGGERorEQ);
                equations.add(equation);
                
                
                /**
                 * generating graph for functions
                 */
                double x1, y1, x2, y2;
                x1 = Short.MIN_VALUE;
                y1 = ((equation.getRight().getListFactors().get(0)-equation.getLeft().getListFactors().get(0)*x1)/equation.getLeft().getListFactors().get(1));
                x2 = Short.MAX_VALUE;
                y2 = ((equation.getRight().getListFactors().get(0)-equation.getLeft().getListFactors().get(0)*x2)/equation.getLeft().getListFactors().get(1));
                System.out.println(x1+" "+ y1 + " " + x2 + " " + y2);
                graphs.add(new Line(x1, y1, x2, y2, equation.toString()));
            }
            for (int j = 0; j < crossingOfLinePoints.size(); j++) {
                boolean flag = true;
                for (Equation equation : equations) {
                    if (!equation.checkIfItSuites(new double[]{-0.04, 0.27})) {
                        flag = false;
                    }
                }
                if (!flag) {
                    crossingOfLinePoints.remove(j);
                    j--;
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private ArrayRealVector solveEquations(RealMatrix matrixA, RealVector vectorB) {
        DecompositionSolver solver;             // Apache Math solver
        ArrayRealVector solutionOfEquations = null;  // Apache math vectors that stores coefficients of right part of equations and roots of system

        solver = new LUDecompositionImpl(matrixA).getSolver(); // Trying to solve generated system
        solutionOfEquations = (ArrayRealVector) solver.solve(vectorB);
        if (solutionOfEquations == null) {
            return null;
        } else {
            return solutionOfEquations;
        }
    }

    public double[] getSolutionEquationsDualTask() {
        return solutionEquationsDualTask;
    }

    public LinearProgramTask getDualTask() {
        return dualTask;
    }

    public List<Line> getGraphs() {
        return graphs;
    }

    public List<ArrayRealVector> getCrossingOfLinePoints() {
        return crossingOfLinePoints;
    }

    public LinearProgramTask getCanonicalLinearProgramTask() {
        return canonicalLinearProgramTask;
    }
    
    public boolean checkIfTaskHasSpecialFormToCurrentMethod(){
        boolean result;
        if(task.getGoalFunction().getExtremum()==GoalFunction.Extremum.Max){
            result = task.getGoalFunction().checkIfListFactorsToZero(Equation.InequalitySight.SMALLER);
        }else{
            result = task.getGoalFunction().checkIfListFactorsToZero(Equation.InequalitySight.BIGGER);
        }
        if(!result)
            return result;
        for(Equation equation:task.getBusinessRestrictions()){
            result = equation.getInequalitySight().equals(Equation.InequalitySight.BIGGERorEQ) || equation.getInequalitySight().equals(Equation.InequalitySight.BIGGER);
            if(!result)
                break;
        }
        return result;
    }
}
